﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.ApplicationServer.Caching;

namespace VelocityWinClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        public DataCacheServerEndpoint[] GetClusterEndpoints()
        {
            DataCacheServerEndpoint[] cacheCluster;

            if (optUseSingleServerCluster.Checked)
            {
                cacheCluster = new DataCacheServerEndpoint[1];
                cacheCluster[0] = new DataCacheServerEndpoint("localhost", 22233);
            }
            else
            {
                cacheCluster = new DataCacheServerEndpoint[3];
                cacheCluster[0] = new DataCacheServerEndpoint("server1", 22233);
                cacheCluster[1] = new DataCacheServerEndpoint("server2", 22233);
                cacheCluster[2] = new DataCacheServerEndpoint("server3", 22233);

            }

            return cacheCluster;
        }

        private void btnPutInCache_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");
            
            cache.Put("Value1", txtGetPut1.Text);
            txtGetPut1.Text = "";

        }

        private void btnGetFromCache_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");
            
            txtGetPut1.Text = "";

            string result = (string)cache.Get("Value1");

            if (result == null)
                txtGetPut1.Text = "(null)";
            else
                txtGetPut1.Text = result;
            
        }

        private void btnCreateRegion_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            //Always test if region exists;
            try
            {
                cache.CreateRegion("Televisions");
                MessageBox.Show("Region was created!","Create region", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (DataCacheException dcex)
            {
                //if region already exists it's ok, otherwise rethrow the exception
                if (dcex.ErrorCode == DataCacheErrorCode.RegionAlreadyExists)
                    MessageBox.Show("Region already exists!", "Create region",MessageBoxButtons.OK , MessageBoxIcon.Warning);
                else
                    throw dcex;
            }

       }

        private void btnClearRegion_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            cache.ClearRegion("Televisions");
            MessageBox.Show("Region was cleared!", "Clear region", MessageBoxButtons.OK, MessageBoxIcon.Information);

        }

        private void btnAddSampleData_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");
            
            cache.ClearRegion("Televisions");
            
            cache.Put("item1", @"SAMSUNG A6 LCD 40"" FULL-HD 100HZ", new List<DataCacheTag>() { new DataCacheTag("LCD"), new DataCacheTag("100HZ")  }, "Televisions");
            cache.Put("item2", @"PANASONIC PLASMA 46"" FULL-HD 50HZ", new List<DataCacheTag>() { new DataCacheTag("PLASMA") }, "Televisions");
            cache.Put("item3", @"SAMSUNG A7 LCD 37"" FULL-HD 100HZ", new List<DataCacheTag>() { new DataCacheTag("LCD"), new DataCacheTag("100HZ") }, "Televisions");
            cache.Put("item4", @"SONY BRAVIA LCD 40"" FULL-HD 50HZ", new List<DataCacheTag>() { new DataCacheTag("LCD") }, "Televisions");
            cache.Put("item5", @"SHARP AQUOS LED 40"" FULL-HD 50HZ", new List<DataCacheTag>() { new DataCacheTag("LED") }, "Televisions");

            MessageBox.Show("Sample Data added!", "Add Sample Data", MessageBoxButtons.OK, MessageBoxIcon.Information); 
            
        }

        private void btnGetByTag1_Click(object sender, EventArgs e)
        {
            GetObjectsByRegionTag("Televisions", "LCD");
        }

        private void btnGetByTag2_Click(object sender, EventArgs e)
        {
            GetObjectsByRegionTag("Televisions", "PLASMA");
        }

        private void btnGetByTag3_Click(object sender, EventArgs e)
        {
            GetObjectsByRegionTag("Televisions", "100HZ");
        }

        private void btnGetByTag4_Click(object sender, EventArgs e)
        {
            GetObjectsByRegionTag("Televisions", "LED");
        }

        private void GetObjectsByRegionTag(string region, string tag)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            listBox1.DataSource = null;

            IEnumerable<KeyValuePair<string, object>> itemsByTag = cache.GetObjectsByTag(new DataCacheTag(tag), region);

            listBox1.DataSource = itemsByTag.ToList();
        }

        private void btnGetByRegion_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            listBox1.DataSource = null;

            IEnumerable<KeyValuePair<string, object>> itemsInRegion  = cache.GetObjectsInRegion("Televisions");

            listBox1.DataSource = itemsInRegion.ToList();

        }
   
        public void OnNotificationReceived(string CacheName, 
                                           string regionName, 
                                           string key, 
                                           DataCacheItemVersion version, 
                                           DataCacheOperations cacheOperation,
                                           DataCacheNotificationDescriptor nd)
        {
            if (string.IsNullOrEmpty(regionName)) regionName = "Default";

            string message = string.Format(@"{0} {1} in Cache: ""{2}"" Region: ""{3}""", cacheOperation.ToString(),
                                                                                         key,
                                                                                         CacheName, 
                                                                                         regionName);


            Action<string> LogNotification = m => { lstNotifications.Items.Add(m); };
            Invoke(LogNotification, new object[] { message });

        }
       
        private void btnEnableNotifications_Click(object sender, EventArgs e)
        {
            //Cache notifications only report data-related changes to regions and cache items in the 
            //cluster, they do not report events of the cluster itself.

            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties(100, new TimeSpan(0,0,10), DataCacheLocalCacheInvalidationPolicy.NotificationBased);
            
            cfg.NotificationProperties = new DataCacheNotificationProperties(1000,new TimeSpan(0, 0, int.Parse(txtPollInterval.Text)));

            DataCacheFactory factory = new DataCacheFactory(cfg);
           
            DataCache cache = factory.GetCache("DemoCache");        

            //Which operations to listen to?
            DataCacheOperations allCacheOperations =  DataCacheOperations.AddItem |
                                                      DataCacheOperations.ReplaceItem |
                                                      DataCacheOperations.RemoveItem |
                                                      DataCacheOperations.CreateRegion |
                                                      DataCacheOperations.ClearRegion |
                                                      DataCacheOperations.RemoveRegion;

            //provide a notification callback
            DataCacheNotificationCallback cb;
            cb = new DataCacheNotificationCallback(OnNotificationReceived);

            //add cache-level notification callback 
            cache.AddCacheLevelCallback(allCacheOperations, cb);

            btnEnableNotifications.Enabled = false;
            txtPollInterval.Enabled = false;

            MessageBox.Show("Notifications enabled", "Enable Notifications", MessageBoxButtons.OK, MessageBoxIcon.Information);
            
        }

        DataCacheLockHandle lockHandle;

        private void btnGetAndLock_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            try
            {
                txtLocking1.Text = (string)cache.GetAndLock("Value1", 
                                                            new TimeSpan(0, 0, int.Parse(txtTimeout.Text)),
                                                            out lockHandle);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Get and Lock", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }


        }

        private void btnPutAndUnlock_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");
            
            try
            {
                cache.PutAndUnlock("Value1", txtLocking1.Text, lockHandle);
                txtLocking1.Text = "";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Put and unlock", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

        }

        
        private DataCacheItemVersion itemVersion;

        private void btnGetWithVersion_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");
            
            txtLocking2.Text = (string)cache.Get("Value1", out itemVersion);                       

        }

        private void btnPutOptimistic_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            try
            {
                cache.Put("Value1", txtLocking2.Text, itemVersion);
                txtLocking2.Text = "";
            }
            catch (DataCacheException ex)
            {
                MessageBox.Show(ex.Message, "Put optimistic", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void btnAddItem_Click(object sender, EventArgs e)
        {
            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCache");

            List<DataCacheTag> tags = new List<DataCacheTag>();

            tags.Add(new DataCacheTag(cmbDisplayType.Text));
            tags.Add(new DataCacheTag(cmbFrequency.Text));

            cache.Put(Guid.NewGuid().ToString(),
                      string.Format("{0}{1}{2}",txtItemDescription.Text,cmbDisplayType.Text,cmbFrequency.Text),
                      tags,
                      "Televisions");

            txtItemDescription.Text = "";
        }

        private DataCache myCache;

        private void btnCreateCacheClient_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            lstLocalCache.Items.Add(string.Format("------- Creating client with Local Cache Enabled={0} -------", chkLocalCache.Checked.ToString().ToUpper()));

            sw.Start();

            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;

            if (chkLocalCache.Checked)
            {
                if (OptTimeout.Checked)
                {
                    cfg.LocalCacheProperties = new DataCacheLocalCacheProperties(trackBar1.Value, new TimeSpan(0, 0, 10), DataCacheLocalCacheInvalidationPolicy.TimeoutBased);
                }
                else
                {
                    cfg.LocalCacheProperties = new DataCacheLocalCacheProperties(trackBar2.Value, new TimeSpan(0, 0, 10), DataCacheLocalCacheInvalidationPolicy.NotificationBased);
                }
            }
            
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            myCache = factory.GetCache("DemoCache");
            
            sw.Stop();

            lstLocalCache.Items.Add(string.Format("Client created. Time elapsed: {1} ms", trackBar1.Value, sw.ElapsedMilliseconds.ToString()));          

        }

        private void btnLoadFakeData_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            //putting sample objects in cache
            sw.Start();

            for (int i = 0; i < trackBar1.Value; i++)
            {
                myCache.Put(string.Format("Item{0}", i),
                            string.Format("SampleItemValue{0}", i));
            }

            sw.Stop();

            lstLocalCache.Items.Add(string.Format("{0} items loaded in cache. Time elapsed: {1} ms ({2} Puts/s)",
                                                  trackBar1.Value, 
                                                  sw.ElapsedMilliseconds.ToString(),
                                                  (GetRate(trackBar1.Value, sw.ElapsedMilliseconds)).ToString()));
        }

        

        private void btnStartTest_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
             
            string str;

            sw.Start();

            for (int i = 0; i < trackBar1.Value; i++)
            {
                str = (string)myCache.Get(string.Format("Item{0}", i));
                if (str == null)
                    throw new Exception("Item not found");

                //if (str != string.Format("SampleItemValue{0}", i))
                //    lstLocalCache.Items.Add(string.Format("item {0} was modified", i));


            }


            sw.Stop();


            lstLocalCache.Items.Add(string.Format("{0} items retrieved from cache. Time elapsed: {1} ms ({2} Gets/s)",
                                                  trackBar1.Value,
                                                  sw.ElapsedMilliseconds.ToString(),
                                                  GetRate(trackBar1.Value, sw.ElapsedMilliseconds).ToString()));

        }

        private int GetRate(long count, long timeElapsedms)
        {
            decimal rate= decimal.Divide(count,timeElapsedms);

            return (int)(rate*1000);
        }

        private void btnParallelPut_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            //putting sample objects in cache
            sw.Start();

            Parallel.For(0, trackBar1.Value, i =>
            {
                myCache.Put(string.Format("Item{0}", i),
                            string.Format("SampleItemValue{0}", i));
            });

            sw.Stop();

            lstLocalCache.Items.Add(string.Format("{0} items loaded in cache. Time elapsed: {1} ms ({2} Puts/s) using Parallel.For",
                                                  trackBar1.Value,
                                                  sw.ElapsedMilliseconds.ToString(),
                                                  (GetRate(trackBar1.Value, sw.ElapsedMilliseconds)).ToString()));
        }

        private void btnParallelGet_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            string str;

            sw.Start();

            Parallel.For(0, trackBar1.Value, i =>
            {
                str = (string)myCache.Get(string.Format("Item{0}", i));
                if (str == null)
                    throw new Exception("Item not found");

            });


            sw.Stop();

            lstLocalCache.Items.Add(string.Format("{0} items retrieved from cache. Time elapsed: {1} ms ({2} Gets/s) using Parallel.For",
                                                  trackBar1.Value,
                                                  sw.ElapsedMilliseconds.ToString(),
                                                  GetRate(trackBar1.Value, sw.ElapsedMilliseconds).ToString()));
        }

        private void button1_Click_1(object sender, EventArgs e)
        {
            lstLocalCache.Items.Clear();
        }


        private void button1_Click(object sender, EventArgs e)
        {
            if (optUseSingleServerCluster.Checked)
            {
                MessageBox.Show("at least 3 servers needed for HA!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCacheHA");

            //putting sample objects in cache
            trackBar1.Value = 10;

            for (int i = 0; i < trackBar1.Value; i++)
            {
                myCache.Put(string.Format("Item{0}", i),
                            string.Format("SampleItemValue{0}", i));
            }

        }

        private void btnStartTestHA_Click(object sender, EventArgs e)
        {
            if (optUseSingleServerCluster.Checked)
            {
                MessageBox.Show("at least 3 servers needed for HA!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            //ok, it's the same stuff, an helper class would be a better approach!
            DataCacheServerEndpoint[] cluster = GetClusterEndpoints();

            DataCacheFactoryConfiguration cfg = new DataCacheFactoryConfiguration();
            cfg.Servers = cluster;
            cfg.LocalCacheProperties = new DataCacheLocalCacheProperties();
            cfg.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None,
                                                           DataCacheProtectionLevel.None);

            DataCacheFactory factory = new DataCacheFactory(cfg);
            DataCache cache = factory.GetCache("DemoCacheHA");

            lstHATest.Items.Clear();

            string str;

            //Getting items from cache

            for (int i = 0; i < trackBar1.Value; i++)
            {
                str = (string)myCache.Get(string.Format("Item{0}", i));

                if (str == null)
                    throw new Exception("Item not found");
                else
                    lstHATest.Items.Add(string.Format("Item{0} - {1}", i, str));

            }


        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            txtItemCount.Text = trackBar1.Value.ToString("n0"); 
        }

        private void trackBar2_Scroll(object sender, EventArgs e)
        {
            TxtLocalItemsCount.Text = trackBar2.Value.ToString("n0"); 
        }

        private void chkLocalCache_CheckedChanged(object sender, EventArgs e)
        {
            OptNotifications.Enabled = chkLocalCache.Checked;
            OptTimeout.Enabled = chkLocalCache.Checked;
        }

        private void btnModifyItem_Click(object sender, EventArgs e)
        {
            for(int i=0 ;i<1000;i++)
                myCache.Put(string.Format("Item{0}",i), "Modified");

        }


    }
}
